///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_CSTATICDISPATCHER_H_
#define _BUOLA_UTILITY_CSTATICDISPATCHER_H_

#include <buola/buola.h>
#include <buola/traits/tuple.h>

namespace buola {

namespace NPrivate
{
    template <class tSomeLH,class tSomeRH,class tExecutor>
    struct EInvocationTraits
    {
        static typename tExecutor::tResultType DoDispatch(
                                    tSomeLH& pLH,tSomeRH& pRH,
                                    tExecutor &pExec,std::false_type)
        {
            return pExec.Fire(pLH,pRH);
        }

        static typename tExecutor::tResultType DoDispatch(
                                    tSomeLH& pLH,tSomeRH& pRH, 
                                    tExecutor &pExec,std::true_type)
        {
            return pExec.Fire(pRH,pLH);
        }
    };
}

template<class tExecutor>
class CStaticDispatcher
{
    typedef typename tExecutor::tResultType tResultType;
    typedef typename tExecutor::tBaseTypeLH tBaseLH;
    typedef typename tExecutor::tBaseTypeRH tBaseRH;
    typedef typename tExecutor::tTypesLH tTypesLH;
    typedef typename tExecutor::tTypesRH tTypesRH;

    template <class tSomeLH>
    static tResultType DispatchRH(tSomeLH &pLH,tBaseRH &pRH,tExecutor pExec,MTypeList<>)
    {
        throw XInvalid("error in EStaticDispatcher");
    }
        
    template <class tSomeLH,typename tFirst,typename... tRest>
    static tResultType DispatchRH(tSomeLH &pLH,tBaseRH &pRH,tExecutor pExec,MTypeList<tFirst,tRest...>)
    {           
        if (tFirst* p2 = dynamic_cast<tFirst*>(&pRH))
        {
            return pExec.Fire(pLH,*p2);
        }
        return DispatchRH(pLH,pRH,pExec,MTypeList<tRest...>());
    }
        
    static tResultType DispatchLH(tBaseLH &pLH,tBaseRH &pRH,tExecutor pExec,MTypeList<>)
    {
        throw XInvalid("error in EStaticDispatcher");
    }
        
    template <typename tFirst,typename... tRest>
    static tResultType DispatchLH(tBaseLH &pLH,tBaseRH &pRH,tExecutor pExec,MTypeList<tFirst,tRest...>)
    {           
        if (tFirst* p1 = dynamic_cast<tFirst*>(&pLH))
        {
            return DispatchRH(*p1,pRH,pExec,tTypesRH());
        }
        return DispatchLH(pLH,pRH,pExec,MTypeList<tRest...>());
    }
    
public:
    static tResultType Go(tBaseLH &pLH,tBaseRH &pRH,tExecutor pExec)
    {
        return DispatchLH(pLH,pRH,pExec,tTypesLH());
    };

    static tResultType Go(tBaseLH &pLH,tBaseRH &pRH)
    {
        return DispatchLH(pLH,pRH,tExecutor(),tTypesLH());
    };
};

template<class tExecutor>
class CSymmetricDispatcher
{
    typedef typename tExecutor::tResultType tResultType;
    typedef typename tExecutor::tBaseType tBase;
    typedef typename tExecutor::tTypes tTypes;

    template <class tSome>
    static tResultType DispatchRH(tSome &pLH,tBase &pRH,tExecutor pExec,MTypeList<>)
    {
        throw XInvalid("error in EStaticDispatcher");
    }
        
    template <typename tSome,typename tFirst,typename... tRest>
    static tResultType DispatchRH(tSome &pLH,tBase &pRH,tExecutor pExec,MTypeList<tFirst,tRest...>)
    {           
        if (tFirst* p2 = dynamic_cast<tFirst*>(&pRH))
        {
            std::integral_constant<bool,MIndexInTypeList<tFirst,tTypes>::value <
                          MIndexInTypeList<tSome,tTypes>::value> lI2T;

            typedef NPrivate::EInvocationTraits<tSome,tFirst,tExecutor> 
                                                    tCallTraits;
                    
            return tCallTraits::DoDispatch(pLH,*p2,pExec,lI2T);
        }
        return DispatchRH(pLH,pRH,pExec,MTypeList<tRest...>());
    }
        
    static tResultType DispatchLH(tBase &pLH,tBase &pRH,tExecutor pExec,MTypeList<>)
    {
        throw XInvalid("error in EStaticDispatcher");
    }
        
    template <typename tFirst,typename... tRest>
    static tResultType DispatchLH(tBase &pLH,tBase &pRH,tExecutor pExec,MTypeList<tFirst,tRest...>)
    {           
        if (tFirst* p1 = dynamic_cast<tFirst*>(&pLH))
        {
            return DispatchRH(*p1,pRH,pExec,tTypes());
        }
        return DispatchLH(pLH,pRH,pExec,MTypeList<tRest...>());
    }
    
public:
    static tResultType Go(tBase &pLH,tBase &pRH,tExecutor pExec)
    {
        return DispatchLH(pLH,pRH,pExec,tTypes());
    };

    static tResultType Go(tBase &pLH,tBase &pRH)
    {
        return DispatchLH(pLH,pRH,tExecutor(),tTypes());
    };
};

//TODO:
//a matrix-based dispatcher (similar to the one in alexandrescu's book) can be
//implemented using exactly the same interface as this one. the first call to
//Go will create a matrix, which will be filled with calls to a templatised
//function which will be generated for every pair. These templatised functions 
//will call the executor 

/*namespace buola*/ }

#endif
